﻿namespace NJQuery {
	using System;
	using System.Web.UI;
	using System.Web.UI.WebControls;
	using System.Collections.Generic;
	using System.Collections.Specialized;
	using System.Text;
	using System.ComponentModel;
	using System.Globalization;
	using System.Diagnostics.CodeAnalysis;

	/// <summary>
	/// Server control used to generate a static <see href="http://jqueryui.com/themeroller/">jQuery UI Themed</see> panel.
	/// </summary>
	[ParseChildren(true), PersistChildren(false)]
	public class ThemedPanel : ManagedWebControl, IPostBackDataHandler {


		#region Constants: Property defaults

		private const PanelType defaultPanelType = PanelType.Content;
		private const bool defaultHighlight = false;
		private const bool defaultShowErrorState = false;
		private const Priority defaultPriority = Priority.None;

		#endregion


		#region Fields

		private ITemplate content;
		private GroupControlContainer contentContainer;
		private ResizableOptions resizable;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="ThemedPanel"/> class.
		/// </summary>
		public ThemedPanel() : base(HtmlTextWriterTag.Div) { }

		#endregion


		#region Properties: Theme

		/// <summary>
		/// Gets or sets a value representing logical jQuery UI component containers defined by the theme framework.
		/// </summary>
		/// <exception cref="ArgumentException">Value not defined.</exception>
		[DefaultValue(defaultPanelType), Category("Appearance")]
		public PanelType PanelType {
			get { return ViewState["PanelType"] as PanelType? ?? defaultPanelType; }
			set {
				if(!Enum.IsDefined(typeof(PanelType), value)) throw new ArgumentException("Value not defined.", "value");

				ViewState["PanelType"] = value;
			}
		}

		/// <summary>
		/// Gets or sets whether or not the control should be rendered with a highlighted interaction cue defined by the theme framework.
		/// </summary>
		[DefaultValue(defaultHighlight), Category("Appearance")]
		public virtual bool Highlight {
			get { return ViewState["Highlight"] as bool? ?? defaultHighlight; }
			set { ViewState["Highlight"] = value; }
		}

		/// <summary>
		/// Gets or sets whether or not the control should be rendered with an error interaction cue defined by the theme framework.
		/// </summary>
		[DefaultValue(defaultShowErrorState), Category("Appearance")]
		public virtual bool ShowErrorState {
			get { return ViewState["ShowErrorState"] as bool? ?? defaultShowErrorState; }
			set { ViewState["ShowErrorState"] = value; }
		}

		/// <summary>
		/// Gets or sets a value representing priority-based interaction cues defined by the theme framework.
		/// </summary>
		/// <exception cref="ArgumentException">Value not defined.</exception>
		[DefaultValue(defaultPriority), Category("Appearance")]
		public virtual Priority Priority {
			get { return ViewState["Priority"] as Priority? ?? defaultPriority; }
			set {
				if(!Enum.IsDefined(typeof(Priority), value)) throw new ArgumentException("Value not defined.", "value");

				ViewState["Priority"] = value;
			}
		}

		#endregion


		#region Properties: Templates

		/// <summary>
		/// Gets or sets the template that defines the content of the <see cref="Dialog"/> control.
		/// </summary>
		[TemplateInstance(TemplateInstance.Single), PersistenceMode(PersistenceMode.InnerProperty), Browsable(false)]
		public ITemplate Content {
			get { return content; }
			set { content = value; }
		}

		/// <summary>
		/// Gets a control object to which you can programmatically add child controls for <see cref="Content"/>.
		/// </summary>
		[Browsable(false)]
		public Control ContentContainer {
			get {
				EnsureChildControls();
				return contentContainer;
			}
		}

		#endregion


		#region Properties: Interaction Initialization

		[DefaultValue(null), MergableProperty(false), PersistenceMode(PersistenceMode.InnerProperty), Category("Behavior")]
		public virtual ResizableOptions Resizable {
			get {
				if(resizable == null) resizable = CreateResizable();
				return resizable;
			}
		}

		#endregion


		#region Properties: Protected explicit implementation

		/// <summary>
		/// Gets whether or not the control should be rendered with jQuery UI theme classes.
		/// </summary>
		protected virtual bool UseTheme {
			get { return true; }
		}

		#endregion


		#region Properties: Hidden Field

		/// <summary>
		/// Gets the rendered element ID of the hidden field used to round-trip any modified style values.
		/// </summary>
		public virtual string StyleClientId {
			get { return string.Format(CultureInfo.InvariantCulture, "__{0}_Style", ClientID); }
		}

		#endregion


		#region Methods: Child controls

		/// <summary>
		/// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering.
		/// </summary>
		protected override void CreateChildControls() {
			base.CreateChildControls();

			if(contentContainer == null) {
				contentContainer = CreateContentContainer();
				GroupedControls.AddContainer(contentContainer);
			}

		}

		/// <summary>
		/// Creates the control object to which the user can programmatically add child controls for <see cref="Content"/>.
		/// </summary>
		/// <returns>The <see cref="GroupControlContainer"/> to group the child controls.</returns>
		protected virtual GroupControlContainer CreateContentContainer() {
			return new GroupControlContainer(this);
		}

		protected virtual ResizableOptions CreateResizable() {
			return new ResizableOptions(ViewState);
		}

		#endregion


		#region Methods: Rendering hooks

		/// <summary>
		/// Raises the <see cref="Control.Init"/> event.
		/// </summary>
		/// <param name="e">The <see cref="EventArgs"/> object that contains the event data.</param>
		protected override void OnInit(EventArgs e) {
			base.OnInit(e);

			if(Content != null) Content.InstantiateIn(ContentContainer);

		}

		/// <summary>
		/// Raises the <see cref="Control.PreRender"/> event.
		/// </summary>
		/// <param name="e">The <see cref="EventArgs"/> object containing the event data.</param>
		protected override void OnPreRender(EventArgs e) {
			base.OnPreRender(e);

			if(Visible) Page.RegisterRequiresPostBack(this);
		}

		/// <summary>
		/// Allows a jQuery server control to reference scripts and stylesheets necessary to render their client markup and widgets.
		/// </summary>
		/// <param name="manager">The <see cref="Manager"/> used to handle style and script referencing in addition to unified generation of load and ready scripts.</param>
		protected override void Register(Manager manager) {

			bool resizable = this.resizable != null && this.resizable.Enabled;

			manager.RegisterClientScript(ClientScript.Core);
			manager.RegisterClientScript(ClientScript.UICore);
			if(resizable) {
				manager.RegisterClientScript(ClientScript.UIWidgetFactory);
				manager.RegisterClientScript(ClientScript.UIMouseUtility);
				manager.RegisterClientScript(ClientScript.UIResizableInteraction);

				manager.RegisterClientStyle(ClientStyle.UICore);
				manager.RegisterClientStyle(ClientStyle.UIResizableInteraction);
			}
			manager.RegisterTheme();

			if(resizable) manager.RegisterForReadyScript(this);
			manager.RegisterForSubmitScript(this);
		}

		/// <summary>
		/// Sends server control content to a provided <see cref="HtmlTextWriter"/> object, which writes the content to be rendered to the client.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="writer">A <see cref="HtmlTextWriter"/> that represents the output stream to render HTML content on the client.</param>
		protected override void Render(HtmlTextWriter writer) {
			base.Render(writer);

			ClientScriptManager clientScript = Page.ClientScript;
			clientScript.RegisterForEventValidation(new PostBackOptions(this));

			//TODO: Implement
			clientScript.RegisterHiddenField(StyleClientId, "");

		}

		protected override void RenderContents(HtmlTextWriter writer) {

			StringBuilder cssClasses = new StringBuilder();
			foreach(string cssClass in GetInnerCssClasses()) {
				if(cssClasses.Length != 0) cssClasses.Append(" ");
				cssClasses.Append(cssClass);
			}

			if(cssClasses.Length != 0) writer.AddAttribute(HtmlTextWriterAttribute.Class, cssClasses.ToString());
			writer.RenderBeginTag(HtmlTextWriterTag.Div);

			base.RenderContents(writer);

			writer.RenderEndTag();

		}

		/// <summary>
		/// Enumerates additional CSS classes to be added to the HTML tag.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <returns>The additional CSS classes.</returns>
		protected override IEnumerable<string> GetAdditionalCssClasses() {

			yield return "ui-helper-clearfix";

			string panelTypeCssClass = PanelType.GetCssClass();
			if(!string.IsNullOrEmpty(panelTypeCssClass)) yield return panelTypeCssClass;

			yield return "ui-corner-all";

		}

		/// <summary>
		/// Renders the jQuery "Ready Event" script required to generate the client-side jQuery widget.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A <see cref="ScriptBuilder"/> that creates the output stream to render jQuery JavaScript on the client.</param>
		protected override void RenderReadyScript(ScriptBuilder script) {
			RenderResizableInitScript(script);
		}

		protected virtual void RenderResizableInitScript(ScriptBuilder script) {

			string clientID = ClientID;

			script.Append("$(");
			script.AppendValue(string.Format(CultureInfo.InvariantCulture, "#{0} > div", clientID));
			script.Append(").resizable(");
			PropertyCollection resizableProperties = new PropertyCollection();
			resizable.AddInitProperties(resizableProperties);
			resizableProperties.AddValue("alsoResize", "#" + clientID);
			script.Append(resizableProperties.ToClientSource(script.Indent));
			script.Append(")");
		}

		#endregion


		#region Methods: Post data

		/// <summary>
		/// Processes postback data for an ASP.NET server control.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="postDataKey">The key identifier for the control.</param>
		/// <param name="postCollection">The collection of all incoming name values.</param>
		/// <returns>True if the server control's state changes as a result of the postback; otherwise, false.</returns>
		protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) {

			Page.ClientScript.ValidateEvent(postDataKey);
			bool changed = false;

			string newStyle = postCollection[StyleClientId];
			//TODO: Do something with it

			return changed;
		}

		/// <summary>
		/// Signals the server control to notify the ASP.NET application that the state of the control has changed.
		/// This method is used primarily by control developers.
		/// </summary>
		[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This method is amazing.  You get a complaint if you don't include the protected one, but when you do, it tells you to use events.  I included it simply because it gives the derrived control a chance to hook deeper.")]
		protected virtual void RaisePostDataChangedEvent() {
			//TODO: Implement
		}

		#endregion


		#region Methods: Private Routines

		private IEnumerable<string> GetInnerCssClasses() {

			if(!Enabled) yield return "ui-state-disabled";
			if(Highlight) yield return "ui-state-highlight";
			if(ShowErrorState) yield return "ui-state-error";

			string priorityCssClass = Priority.GetCssClass();
			if(!string.IsNullOrEmpty(priorityCssClass)) yield return priorityCssClass;

			yield return "ui-corner-all";

		}

		#endregion


		#region Explicit Implementation: IPostBackDataHandler

		bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection) {
			return LoadPostData(postDataKey, postCollection);
		}

		void IPostBackDataHandler.RaisePostDataChangedEvent() {
			RaisePostDataChangedEvent();
		}

		#endregion


	}

}